package com.facebook.datasource;

import android.util.Log;

import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Supplier;

import java.util.List;

import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public final class FirstAvailableDataSourceSupplier<T> implements Supplier<AbstractDataSource<T>> {
    @ThreadSafe
    final class FirstAvailableDataSource extends AbstractDataSource<T> {

        final class InternalDataSubscriber implements DataSubscriber<T> {

            public final void onNewResult(AbstractDataSource<T> dataSource) {
                if (dataSource.hasResult()) {
                    boolean isFinished = dataSource.isFinished();
                    synchronized (FirstAvailableDataSource.this) {
                        if (dataSource == FirstAvailableDataSource.this.mCurrentDataSource) {
                            if (dataSource != FirstAvailableDataSource.this.mDataSourceWithResult) {
                                if (FirstAvailableDataSource.this.mDataSourceWithResult == null) {
                                    AbstractDataSource<T> dataSourceWithResult = FirstAvailableDataSource.this.mDataSourceWithResult;
                                    FirstAvailableDataSource.this.mDataSourceWithResult = dataSource;
                                    FirstAvailableDataSource.this.closeSafely(dataSourceWithResult);
                                } else if (isFinished) {
                                    AbstractDataSource<T> dataSourceWithResult = FirstAvailableDataSource.this.mDataSourceWithResult;
                                    FirstAvailableDataSource.this.mDataSourceWithResult = dataSource;
                                    FirstAvailableDataSource.this.closeSafely(dataSourceWithResult);
                                } else {
                                    FirstAvailableDataSource.this.closeSafely(null);
                                }
                            }
                        }
                    }
                    if (dataSource == FirstAvailableDataSource.this.getDataSourceWithResult()) {
                        FirstAvailableDataSource.this.setResult(null, dataSource.isFinished());
                    }
                } else {
                    if (dataSource.isFinished()) {
                        FirstAvailableDataSource.this.onDataSourceFailed(dataSource);
                    }
                }
            }

            public final void onFailure(AbstractDataSource<T> dataSource) {
                FirstAvailableDataSource.this.onDataSourceFailed(dataSource);
            }
        }

        AbstractDataSource<T> mCurrentDataSource = null;
        AbstractDataSource<T> mDataSourceWithResult = null;
        private int mIndex;

        public FirstAvailableDataSource() {
            this.mIndex = 0;
            if (!this.startNextDataSource()) {
                this.setFailure(new RuntimeException("No data source supplier or supplier returned null."));
            }
        }

        void closeSafely(AbstractDataSource dataSource) {
            if (dataSource != null) {
                dataSource.close();
            }
        }

        void onDataSourceFailed(AbstractDataSource dataSource) {
            if (!FirstAvailableDataSource.this.clearCurrentDataSource(dataSource)) {
                return;
            }
            if (dataSource != FirstAvailableDataSource.this.getDataSourceWithResult()) {
                closeSafely(dataSource);
            }
            if (!startNextDataSource()) {
                setFailure(dataSource.getFailureCause());
            }
        }

        @Nullable
        public synchronized final T getResult() {
            AbstractDataSource<T> dataSource = this.getDataSourceWithResult();
            if (dataSource != null) {
                return dataSource.getResult();
            } else {
                return null;
            }
        }

        private synchronized boolean setCurrentDataSource(AbstractDataSource<T> dataSource) {
            if (this.isClosed()) {
                return false;
            }
            this.mCurrentDataSource = dataSource;
            return true;
        }

        public synchronized final boolean hasResult() {
            AbstractDataSource<T> dataSourceWithResult = this.getDataSourceWithResult();
            if (dataSourceWithResult == null) {
                return false;
            } else if (!dataSourceWithResult.hasResult()) {
                return false;
            }
            return true;
        }

        private synchronized boolean clearCurrentDataSource(AbstractDataSource<T> dataSource) {
            if (this.isClosed()) {
                return false;
            } else if (dataSource == this.mCurrentDataSource) {
                this.mCurrentDataSource = null;
                return true;
            } else {
                return false;
            }
        }

        @Override
        public final boolean close() {
            AbstractDataSource<T> dataSourceWithResult;
            AbstractDataSource<T> currentDataSource;
            synchronized (this) {
                if (!super.close()) {
                    return false;
                } else {
                    currentDataSource = this.mCurrentDataSource;
                    this.mCurrentDataSource = null;
                    dataSourceWithResult = this.mDataSourceWithResult;
                    this.mDataSourceWithResult = null;
                }
            }
            closeSafely(dataSourceWithResult);
            closeSafely(currentDataSource);
            return true;
        }

        @Nullable
        final synchronized AbstractDataSource<T> getDataSourceWithResult() {
            return this.mDataSourceWithResult;
        }

        private boolean startNextDataSource() {
            AbstractDataSource<T> dataSourceSupplier;
            Supplier<AbstractDataSource<T>> supplier = this.getNextSupplier();
            if (supplier != null) {
                dataSourceSupplier = supplier.get();
            } else {
                dataSourceSupplier = null;
            }
            if (this.setCurrentDataSource(dataSourceSupplier) && dataSourceSupplier != null) {
                dataSourceSupplier.subscribe(new InternalDataSubscriber(), CallerThreadExecutor.getInstance());
                return true;
            } else {
                closeSafely(dataSourceSupplier);
                return false;
            }
        }

        @Nullable
        private synchronized Supplier<AbstractDataSource<T>> getNextSupplier() {
            if (this.isClosed()) {
                return null;
            } else if (this.mIndex < FirstAvailableDataSourceSupplier.this.mDataSourceSuppliers.size()) {
                List<Supplier<AbstractDataSource<T>>> supplierList = FirstAvailableDataSourceSupplier.this.mDataSourceSuppliers;
                return supplierList.get(mIndex++);
            } else {
                return null;
            }
        }
    }

    final List<Supplier<AbstractDataSource<T>>> mDataSourceSuppliers;

    public FirstAvailableDataSourceSupplier(List<Supplier<AbstractDataSource<T>>> dataSourceSuppliers) {
        Preconditions.checkArgument(!dataSourceSuppliers.isEmpty(), "List of suppliers is empty!");
        this.mDataSourceSuppliers = dataSourceSuppliers;
    }

    public final AbstractDataSource<T> get() {
        return new FirstAvailableDataSource();
    }

    public final boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (obj instanceof FirstAvailableDataSourceSupplier) {
            return android.support.design.a.equal(this.mDataSourceSuppliers, ((FirstAvailableDataSourceSupplier) obj).mDataSourceSuppliers);
        } else {
            return false;
        }
    }

    public final int hashCode() {
        return this.mDataSourceSuppliers.hashCode();
    }

    public final String toString() {
        return android.support.design.a.toStringHelper(this).add("list", this.mDataSourceSuppliers).toString();
    }
}

